class Point {
    x = 0;
    y = 0;
    constructor(x, y) {
        this.x = x
        this.y = y
    }
    eq (x, y) {
        if (x instanceof Point) {
            return x.x == this.x && x.y == this.y
        }
        return this.x == x && this.y == y
    }

    /**
     * 
     * @param {Point}} point 
     */
    length (point) {
        return Math.abs(this.x - point.x) + Math.abs(this.y - point.y)
    }
}
class MapPoint extends Point {
    constructor(x, y, access = true) {
        super(x, y)
        this.access = access
    }
}
class MyMap {
    /**
     * 
     * @param {number} w 
     * @param {number} h 
     * @param {*} itemClickAction 
     */
    constructor(w = 1, h = 1,
        /**
         * @param {MapPoint} mapPoint 
         */
        itemClickAction = (mapPoint) => { }) {
        this.w = w
        this.h = h
        this.load()
        this.itemClickAction = itemClickAction
        this.itemMoveAction = itemClickAction
        this.mouseDown = false
    }
    toString () {
        return this.mapPoints
    }
    draw () {

        const app = document.getElementById("app")
        const maps = app.getElementsByClassName("map")
        if (maps.length == 0) {
            console.display("没有找到map的div")
            return
        }
        const map = maps[0]
        map.innerHTML = ""
        for (let i = this.mapPoints[0].length - 1; i >= 0; i--) {
            const row = document.createElement('div')
            row.className = "row"
            for (let j = 0; j < this.mapPoints.length; j++) {
                const item = document.createElement("div")
                item.className = "item"
                if (this.mapPoints[j][i]) {
                    if (this.target && this.target.eq(j, i)) {
                        item.className += " target"
                    } else if (this.player && this.player.eq(j, i)) {
                        item.className += " player"
                    } else {
                        item.className += " access"
                    }

                } else {
                    item.className += " not-access"
                }
                item.setAttribute("x", j)
                item.setAttribute("y", i)
                let str = this.drawAction(j, i)
                if (str)
                    item.innerText = str
                item.onclick = () => {
                    const x = +item.getAttribute('x')
                    const y = +item.getAttribute('y')
                    this.itemClickAction(new MapPoint(x, y, this.mapPoints[x][y]))
                }
                item.onmousemove = () => {
                    const x = +item.getAttribute('x')
                    const y = +item.getAttribute('y')
                    this.itemMoveAction && this.itemMoveAction(new MapPoint(x, y, this.mapPoints[x][y]))
                }
                row.appendChild(item)
            }
            map.appendChild(row)
        }

        /**
         * @type {HTMLDivElement}
         */
        map.onmousedown = () => {
            this.mouseDown = true
        }
        map.onmouseup = () => {
            this.mouseDown = false
        }

    }
    drawAction = () => ""
    /**
     * 
     * @param {MapPoint} pos 修改点属性
     */
    set (pos) {
        try {
            this.mapPoints[pos.x][pos.y] = pos.access
        } finally { }
    }
    /**
     * @type {Point}
     */
    target = undefined;
    /**
     * @type {Point}
     */
    player = undefined;
    setTarget (pos) {
        if (pos.x >= this.mapPoints.length || pos.y >= this.mapPoints[0].length) {
            console.display("设置目标失败")
            return
        }
        this.target = pos
    }
    /**
     * 
     * @param {Point} pos 
     * @returns 
     */
    setPlayer (pos) {
        if (pos.x >= this.mapPoints.length || pos.y >= this.mapPoints[0].length) {
            console.display("设置Player失败")
            return
        }
        this.player = pos
    }
    save () {
        window.localStorage.setItem("map", JSON.stringify(this.mapPoints))
        if (this.player) {
            window.localStorage.setItem("player", JSON.stringify(this.player))
        }
        if (this.target) {
            window.localStorage.setItem("target", JSON.stringify(this.target))
        }
    }
    clear () {
        window.localStorage.removeItem("map")
    }
    load () {
        try {
            if ((this.mapPoints = JSON.parse(window.localStorage.getItem("map"))) == null) {
                this.mapPoints = []
                for (let i = 0; i < this.w; i++) {
                    const arr = []
                    for (let j = 0; j < this.h; j++) {
                        arr.push(true)
                    }
                    this.mapPoints.push(arr)
                }
            }
            const player = JSON.parse(window.localStorage.getItem("player"))
            const target = JSON.parse(window.localStorage.getItem("target"))
            if (player) {
                this.player = new Point(player.x, player.y)
                this.target = new Point(target.x, target.y)
            }
        } finally { this.draw() }

    }
}


class AStarPoint extends Point {
    /**
     * 
     * @param {number} x 
     * @param {number} y 
     * @param {AStarPoint} parent 
     */
    constructor(x, y, parent) {
        super(x, y)
        this.parent = parent
        this.f = 0
        /**
         * 点与终点的距离
         */
        this.g = 0
        this.h = 0
    }
    /**
     * @param {Point} player 
     * @param {Point} parent 
     * @returns 
     */
    setF (player, parent) {
        this.g = parent ? parent.g + 1 : 0
        this.h = Math.abs(this.x - player.x) + Math.abs(this.y - player.y)
        this.f = this.g + this.h
        return this.f
    }
}
/**
 * A星算法
 */
class AStarAlgorithm {
    /**
     * 
     * @param {MyMap} map 地图信息
     */
    constructor(map) {
        this.map = map
        this.target = new Point(map.target.x, map.target.y)
        this.player = new Point(map.player.x, map.player.y)


    }
    /**
     * 
     * @param {AStarPoint} point 
     */
    #eachPoint (point) {
        this.eachArray.push(point)
        this.#push(new AStarPoint(point.x, point.y + 1, point))
        this.#push(new AStarPoint(point.x + 1, point.y, point))
        this.#push(new AStarPoint(point.x - 1, point.y, point))
        this.#push(new AStarPoint(point.x, point.y - 1, point))
        this.notEachArray = this.notEachArray.filter(item => !item.eq(point))
    }
    /**
     * 
     * @param {AStarPoint} point 
     * @param {AStarPoint} parent 
     */
    #push (point) {
        if (this.#_isAccess(point)) {
            let arr_point = this.#_in(this.notEachArray, point)
            if (arr_point) {
                // arr_point.parent = this.#getEnclosure(arr_point)
                return
            }
            if (this.#_in(this.eachArray, point)) {
                return
            }
            point.setF(this.player, point.parent)
            point.parent = this.#getEnclosure(point)
            this.notEachArray.push(point)
        }
    }
    /**
     * 
     * @param {*} point 
     * @returns 
     */
    #_isAccess (point) {
        return point.x >= 0 && point.x < this.map.mapPoints.length && point.y >= 0 && point.y < this.map.mapPoints[0].length && this.map.mapPoints[point.x][point.y]
    }
    #getEnclosure (point) {
        const a = this.#_in(this.eachArray, new Point(point.x, point.y + 1)) || this.#_in(this.notEachArray, new Point(point.x, point.y + 1))
        const b = this.#_in(this.eachArray, new Point(point.x + 1, point.y)) || this.#_in(this.notEachArray, new Point(point.x + 1, point.y))
        const c = this.#_in(this.eachArray, new Point(point.x - 1, point.y)) || this.#_in(this.notEachArray, new Point(point.x - 1, point.y))
        const d = this.#_in(this.eachArray, new Point(point.x, point.y - 1)) || this.#_in(this.notEachArray, new Point(point.x, point.y - 1))
        const arr = [a, b, c, d].filter(item => item && this.#_isAccess(item))
        let min = arr[0].g
        let minitem = arr[0]
        for (let item of arr) {
            if (item.g < min) {
                minitem = item
            }
        }
        return minitem
    }
    /**
     * 
     * @param {AStarPoint} point 
     * @returns 
     */
    #getMinFPoint () {
        if (this.notEachArray.length == 0) {
            return null
        }
        let minFPoint = this.notEachArray[0]
        for (let item of this.notEachArray) {
            if (item.h < minFPoint.h && item.f <= minFPoint.f) {
                minFPoint = item
            }
        }
        return minFPoint
    }
    /**
     * @returns {AStarPoint}
     */
    #_in (array, point) {
        for (let item of array) {
            if (point.eq(item))
                return item
        }
        return false
    }

    timer = null;
    build (timeOffset, display) {
        if (this.timer) {
            console.display("正在计算")
            return
        }
        if (this.player.eq(this.target)) {
            console.display("起点和重点在一个位置")
            return
        }
        const _target = new AStarPoint(this.target.x, this.target.y, null)
        _target.setF(this.player, null)
        /**
         * @type {AStarPoint[]}
         */
        this.eachArray = []
        this.notEachArray = [_target]
        this.result = null
        let minFPoint = this.#getMinFPoint(null)
        this.map.drawAction = (x, y) => {
            if (message.showEachArray) {
                for (let i = 0; i < this.eachArray.length; i++) {
                    let item = this.eachArray[i]
                    if (item.eq(x, y)) {
                        return item.g + ""
                    }
                }
            }
            if (message.showNoteachArray) {
                for (let i = 0; i < this.notEachArray.length; i++) {
                    let item = this.notEachArray[i]
                    if (item.eq(x, y)) {
                        return item.g + ""
                    }
                }
            }
        }
        this.timer = setInterval(() => {
            this.#eachPoint(minFPoint)
            minFPoint = this.#getMinFPoint(minFPoint)
            this.map.draw()
            if (minFPoint != null && !minFPoint.eq(this.player)) {
            } else {
                clearInterval(this.timer)
                if (minFPoint == null) {
                    console.display("这是不可达的")
                    this.result = null
                }
                console.display("计算成功")
                this.result = new AStarResult(minFPoint, this.map)
                display && display()
            }
        }, timeOffset ? timeOffset : 0)
    }
    result = null
}

class AStarResult {
    /**
     * @param {AStarPoint} point
     * @param {MyMap} map 
     */
    constructor(point, map) {
        this.point = point
        this.map = map
        this.over = false
    }

    /**
     * 下一步
     */
    next () {
        if (this.point.parent == null) {
            this.over = true
            return
        }
        this.map.setPlayer(this.point.parent)
        this.point = this.point.parent
        this.map.draw()
    }
}


const message = {
    run: false,
    calculation: false,
    editCalculationTime: 10,
    editRunTime: 20,
    showEachArray: true,
    showNoteachArray: false
}
/**
 * 0 :修改地形
 * 1 :修改起点位置
 * 2 :修改终点位置
 */
let mouseState = 0


console.display = (message) => {
    log.innerText = message
}

showEachArray.onchange = e => message.showEachArray = e.target.checked
showNoteachArray.onchange = e => message.showNoteachArray = e.target.checked


const main = (w, h) => {
    const map = new MyMap(w, h)
    map.itemClickAction = (mapPoint, access) => {
        if (map.target.eq(mapPoint) || map.player.eq(mapPoint)) {
            return
        }
        if (message.run) {
            console.display("正在寻路，请不要改动")
            return
        }
        if (message.calculation) {
            console.display("正在计算，请不要改动")
            return
        }
        switch (mouseState) {
            case 0: {
                mapPoint.access = access !== undefined ? access : !mapPoint.access
                map.set(mapPoint)
                break
            }
            case 1: {
                if (!mapPoint.access) {
                    return
                }
                map.setPlayer(mapPoint)
                break
            }
            case 2: {
                if (!mapPoint.access) {
                    return
                }
                map.setTarget(mapPoint)
                break
            }
        }
        map.draw()
    }
    map.itemMoveAction = (mapPoint) => {
        if (map.mouseDown) {
            map.itemClickAction(mapPoint, false)
        }
    }
    if (!map.player) {
        map.setPlayer(new Point(1, 1))

    }
    if (!map.target) {
        map.setTarget(new Point(10, 10))
    }
    map.draw()
    let aStarAlgorithm = null
    /**
     * @type {AStarResult}
     */

    start_exec.onclick = () => {
        if (aStarAlgorithm) {
            aStarAlgorithm.timer && clearInterval(aStarAlgorithm.timer)
        }

        aStarAlgorithm = new AStarAlgorithm(map)
        message.calculation = true
        aStarAlgorithm.build(message.editCalculationTime, () => {
            message.calculation = false
        })

    }
    let timer = null
    start_run.onclick = () => {
        if (timer || message.run) {
            console.display('正在寻路')
            return
        }
        if (!aStarAlgorithm || !aStarAlgorithm.result) {
            start_exec.click()
            console.display("还没计算，现在开始计算")
            return
        }
        let result = aStarAlgorithm.result
        message.run = true
        console.display("开始寻路")
        timer = setInterval(() => {
            result.next()
            if (result.over) {
                clearInterval(timer)
                timer = null
                message.run = false
                console.display("寻路成功")
                aStarAlgorithm = null
                result = null
            }
        }, message.editRunTime)
    }
    save_map.onclick = () => map.save()
    clear_map.onclick = () => {
        map.clear()
        map.load()
    }

    edit_item.onclick = () => mouseState = 0
    edit_start.onclick = () => mouseState = 1
    edit_target.onclick = () => mouseState = 2
    const loadNumber = (title) => {
        const data = +prompt(title)
        if (isNaN(data)) {
            alert("这是不是一个有效的数字")
            return loadNumber()
        }
        return data
    }
    edit_run_time.onclick = () => {
        let num = loadNumber("请输入每一步寻路时间（毫秒）")
        message.editRunTime = num
        console.log(message)
    }
    edit_calculation_time.onclick = () => {
        let num = loadNumber("请输入每一步技术时间（毫秒）")
        message.editCalculationTime = num
    }
}

main(40, 40)


